package com.libre.security;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.libre.auth.entity.Role;
import com.libre.auth.mapper.RoleMapper;
import com.libre.common.constant.SysConstant;
import com.libre.common.enums.ResultEnum;
import com.libre.common.exception.BusinessRuntimeException;
import com.libre.common.utils.JsonUtils;
import com.libre.common.utils.StringUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

// 3.校验 token
@Slf4j
public class JWTAuthenticationFilter extends OncePerRequestFilter {

    //JWT签名加密key
    private String jwtSignKey;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RoleMapper roleMapper;
    //超时时长
    @Value("${auth.expire-time}")
    private Integer expireTime = 30;

    public JWTAuthenticationFilter(String jwtSignKey) {
        this.jwtSignKey = jwtSignKey;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info(">>>>>>>>> 进入TOKEN过滤器 >>>>>>>>>");
        //放过一下路径 ，含有accessToken也要放过
        String uri = request.getRequestURI();
        System.out.println(">>>>>>>>>>URI>>>>>>>>>>" + uri);
        if ("/login".equals(uri) || uri.startsWith("/static/resource")) {
            chain.doFilter(request, response);
            return;
        }
        try {
            // 进行校验信息
            UsernamePasswordAuthenticationToken authentication = getAuthentication(request, response);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (Exception e) {
            throw new BusinessRuntimeException("校验用户信息失败：" + e);
        }
        chain.doFilter(request, response);
    }


    //在页面中输入用户名和密码之后首先会进入到UsernamePasswordAuthenticationToken验证(Authentication)
    public UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) {
        log.info(">>>>>>>>> 进入身份验证 >>>>>>>>>");
        String header = request.getHeader(SysConstant.TOKEN_NAME);
        if (StringUtils.isBlank(header)) {
            header = request.getParameter(SysConstant.TOKEN_NAME);
        }
        Boolean notValid = StringUtils.isBlank(header) || !header.startsWith(SysConstant.TOKEN_SPLIT);
        if (notValid) {
            /*new ResponseUtil(response, ResultEnums.RESOLVE_TOKEN_FAILURE.getCode(), false, ResultEnums.RESOLVE_TOKEN_FAILURE.getReasonPhraseCN(), null);//TOKEN信息不存在，或错误
            return null;*/
            throw new BusinessRuntimeException(ResultEnum.RESOLVE_TOKEN_FAILURE.getReasonPhraseCN());//TOKEN信息不存在，或错误
        }
        // 用户名
        String username = null;
        // 权限
        List<GrantedAuthority> authorities = new ArrayList<>();
        // JWT
        try {
            // 解析token
            Claims claims = Jwts.parser()
                    .setSigningKey(jwtSignKey)
                    .parseClaimsJws(header.replace(SysConstant.TOKEN_SPLIT, ""))
                    .getBody();
            // 获取用户名
            username = claims.getSubject();
            // username = claims.get("sub") + "";
        } catch (ExpiredJwtException e) {
            /*new ResponseUtil(response, ResultEnums.LOGIN_HAS_EXPIRED.getCode(), false,
                    ResultEnums.LOGIN_HAS_EXPIRED.getReasonPhraseCN(), null);//登录已失效，请重新登录*/
            throw new BusinessRuntimeException(ResultEnum.LOGIN_HAS_EXPIRED.getReasonPhraseCN());//登录已失效，请重新登录
        } catch (Exception e) {
            /*new ResponseUtil(response, ResultEnums.RESOLVE_TOKEN_FAILURE.getCode(), false,
                    ResultEnums.RESOLVE_TOKEN_FAILURE.getReasonPhraseCN(), null);//解析TOKEN失败，请重新登录*/
            throw new BusinessRuntimeException(ResultEnum.RESOLVE_TOKEN_FAILURE.getReasonPhraseCN());//解析TOKEN失败，请重新登录
        }
        if (StringUtils.isNotBlank(username)) {
            String token = redisTemplate.opsForValue().get(SysConstant.REDIS_ACCESS_TOKEN + username);
            if (!header.equals(token)) {
                throw new BusinessRuntimeException("登录超时，请重新登录");
            }
            // 角色赋值
            String r = redisTemplate.opsForValue().get(SysConstant.REDIS_ROLE + username);
            if (StringUtil.isNotEmpty(r)) {// redis 中为获取到用户角色信息，到数据库中获取
                // 权限
                List<SimpleGrantedAuthority> simpleGrantedAuthorities = JsonUtils.jsonToList(r, SimpleGrantedAuthority.class);
                for (SimpleGrantedAuthority role : simpleGrantedAuthorities) {
                    authorities.add(role);
                }
            } else {
                List<Role> roles = roleMapper.findRolesByUsername(username);
                for (Role role : roles) {
                    authorities.add(new SimpleGrantedAuthority(role.getCode()));
                }
                // 角色信息
                redisTemplate.opsForValue().set(SysConstant.REDIS_ROLE + username, JSON.toJSON(authorities).toString(), expireTime, TimeUnit.MINUTES);
            }
            // 踩坑提醒 此处password不能为null
            User principal = new User(username, "", authorities);
            return new UsernamePasswordAuthenticationToken(principal, null, authorities);
        }
        return null;
    }
}

